/*
 * Copyright (c) 2020, yifengling0. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. No personal names or organizations' names associated with the
 *    Atomthreads project may be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
#include "atom.h"
#include "device.h"
#include "console.h"
#include "syslog.h"
#include <string.h>
#include "debug.h"

SLIST_HEAD(Pool, DevNode);
static struct Pool _header;

Device_t *DeviceFind(const char *name)
{
	DevNode* item;

	SLIST_FOREACH(item, &_header, node) {
		Device_t* it = (Device_t*)item;
		if (strcmp(it->desc->name,name)== 0) {
			return it;
		}
	}

	return NULL;
}

static const DeviceProperty* find_property(const Device_t *dev, const char* name)
{
	for (int i = 0; dev->desc->property_list[i].name != NULL; i++) {
		if (strcmp(dev->desc->property_list[i].name, name) == 0) {
			return &dev->desc->property_list[i];
		}
	}

	return NULL;
}

int DeviceInstall( Device_t* device, const DeviceDesc *desc)
{
    if (DeviceFind(desc->name) == NULL) {
        SLIST_INSERT_HEAD(&_header, (DevNode*)device, node);
		device->desc = desc;
        return TRUE;
    }

	return FALSE;    
}

size_t DeviceSetProperty(Device_t *device, const char *name, void* buf, size_t size)
{
    const DeviceProperty *pro = find_property(device, name);

    if( pro != NULL ){
        if(pro->writep != NULL ){
            return pro->writep(device, buf, size);
        }else{
            SYSLOG(LOG_WARNING, "The property '%s' does not have a setter.", name);
        }
    }else{
        SYSLOG(LOG_WARNING, "The property '%s' does not exist.", name);
    }

    return 0;
}

size_t DeviceGetProperty(Device_t *device, const char *name, void* buf, size_t size)
{
    const DeviceProperty *pro = find_property(device, name);

    if( pro != NULL ){
        if(pro->readp != NULL ){
            return pro->readp(device, buf, size);
        }else{
            SYSLOG(LOG_WARNING, "The property '%s' does not have a getter.", name);
        }
    }else{
        SYSLOG(LOG_WARNING, "The property '%s' does not exist.", name);
    }

    return 0;
}

#ifdef COM_USE_CONSOLE  
static enConsoleResult show_devices(int argc, char** argv)
{
	DevNode* item;
	int count = 0;

	KPrint("device list:\r\n");

	SLIST_FOREACH(item, &_header, node) {
		Device_t* it = (Device_t*)item;
		KPrintf("  %s : %s\r\n",  it->desc->name, it->desc->info);
		
		for (int i = 0; i < it->desc->drv_cnt; i++) {
			KPrintf("   - %s \r\n", it->desc->drv_list[i]);
		}
		count++;
		KPrint("\r\n");
	}
	
	return CE_OK;
}

static Device_t* _curr_dev = NULL;

static enConsoleResult device_cmd(int argc, char** argv)
{
	if (argc >= 2) {
		if (strcmp(argv[1], "probe") == 0 && argc == 3) {
			char* name = argv[2];
			_curr_dev = DeviceFind(name);
			if (_curr_dev == NULL) {
				SYSLOG(LOG_WARNING, "device not found!");
			}else{
				char buf[DEVICE_PV_LEN] = {0};
				KPrintf("property             value\r\n");
				KPrintf("-------------------------------\r\n");
				for (int i = 0; _curr_dev->desc->property_list[i].name != NULL; i++) {
					size_t result = 0;
					KPrintf("%-20s", _curr_dev->desc->property_list[i].name);
					if (_curr_dev->desc->property_list[i].readp != NULL) {
						result = DeviceGetProperty(_curr_dev, _curr_dev->desc->property_list[i].name, buf, DEVICE_PV_LEN);
					}
					KPrintf("%s\r\n", result > 0 ? buf : "NULL");
				}
            }
		}
		else if (strcmp(argv[1], "remove") == 0) {
			_curr_dev = NULL;
		}else if (strcmp(argv[1], "read") == 0) {
			if (_curr_dev == NULL) {
				SYSLOG(LOG_WARNING, "device not probe!");
			}
			else {
                char buf[DEVICE_PV_LEN] = {0};
                size_t ret = DeviceGetProperty(_curr_dev, argv[2], buf, DEVICE_PV_LEN);
                SYSLOG(LOG_INFO, "value: %s\r\nresult: %d", buf, ret);
			}
		}else if (strcmp(argv[1], "write") == 0 && argc == 4) {
			if (_curr_dev == NULL) {
				SYSLOG(LOG_WARNING, "device not probe!");
			}
			else {
                size_t ret = DeviceSetProperty(_curr_dev, argv[2], argv[3], strlen(argv[3]));
                SYSLOG(LOG_INFO, "result: %d", ret);
			}
		}
		else {
			SYSLOG(LOG_WARNING, "command error!");
			KPrint("device [probe|remove|write]");
		}
	}
	else {
		KPrint("device [probe|remove|write|read]");
	}

	return CE_OK;
}

#endif

BGEIN_DEF_CMD(_devCommand)
    CMD_DEF_ALISA(showdevices, show_devices, "show devices")
	CMD_DEF_ALISA(device, device_cmd, "device [probe|remove|write]")
END_DEF_CMD()

void DeviceInit(void)
{
	SLIST_INIT(&_header);
    CONSOLE_REG_CMD(_devCommand);
}